package icasue.net.socket.thread;

import com.alibaba.fastjson.JSON;
import icasue.net.base.annotations.NotNull;
import icasue.net.protocol.carfun.common.BizCode;
import icasue.net.protocol.carfun.common.CommandResponse;
import icasue.net.protocol.carfun.common.Const;
import icasue.net.socket.exception.*;
import icasue.net.socket.driver.mdc.MDCommandDriver;
import icasue.net.socket.driver.providers.CommandDriverManager;
import icasue.net.socket.share.MindManger;
import icasue.net.socket.model.register.RegisterInfo;
import icasue.net.protocol.carfun.CarFunProtocol;
import icasue.net.protocol.carfun.commands.Command;
import icasue.net.protocol.carfun.commands.MDCommand;
import icasue.net.socket.thread.state.SocketState;
import icasue.net.socket.utils.Cast;
import icasue.net.socket.utils.LogUtil;
import icasue.net.socket.utils.ProtocolUtil;
import icasue.net.socket.utils.Refection;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
import java.net.Socket;
import java.nio.charset.Charset;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import static icasue.net.protocol.http.common.Const.$COLON;
import static icasue.net.socket.common.Const.$STRING_EMPTY;

/**
 * @Author: Qiao Hang
 * @CreateDate: 2021/3/18 下午5:01
 * @UpdateDate:
 * @Description: CarFunSocket 为线程资源，Acceptor为每一接收到的来自客户端的Socket连接创建一个CarFunSocket线程资源实现非阻塞的客户端交互
 * 这里的交互是由Socket客户端发起的Command，为MDC(Model Define Command)模型定义命令，包含：
 *  1.客户端相关: 客户端终端socket连接等..
 *  2.channel相关: channel创建，加入，断联等...
 */
public class SocketProxy<RInfo extends RegisterInfo> implements Runnable {

    private static final Charset UTF_8 = Charset.forName("UTF-8");
    //客户端连接的Socket
    private volatile Socket socket;
    //注册信息ClsMetadata
    private Class<? extends RInfo> regInfoClass;
    //客户端Socket状态 游离态0 ,绑定态1, 待销毁-1
    private volatile SocketState state;
    //identity.
    private final Long identity;
    //channel and channelId.
    private Long channelId;
    private Channel channel;
    //注册信息的identity
    private RInfo registerInfo;
    //闲置时间
    private LocalDateTime beFreeZone;
    //响应协议构造句柄.
    private static final MethodHandle $protocolBuilder;

    //socket read write.
    private volatile BufferedReader reader = null;
    private volatile OutputStream outStream  = null;


    static {
        Method unreflect = Refection.findDeclareMethod(MethodHandles.Lookup.class, "unreflect", new Class[]{Method.class});
        //protocolBuilder's handle.
        $protocolBuilder = Cast.cast(
                Refection.methodInvoke(
                        unreflect, Refection.methodLookup,
                        Refection.findDeclareMethod(CarFunProtocol.class, "builder", new Class[]{Command.class,Integer.class}))
                , MethodHandle.class
        );
    }


    public SocketProxy(@NotNull Socket socket, @NotNull Long identity, @NotNull Class<? extends RInfo> regInfoClass){
        this.socket = socket;
        this.regInfoClass = regInfoClass;
        this.identity = identity;
        //创建时为游离态
        changeToFree();
    }

    @Override
    public void run() {
        try {
            reader = new BufferedReader(new InputStreamReader(socket.getInputStream(),UTF_8));
            outStream = socket.getOutputStream();
        }catch (Throwable e){
            e.printStackTrace();
            LogUtil.error("SocketProxy open stream with client occur error.");
            /*获取客户端输入流异常，服务端销毁SocketProxyRunner, 退出Socket线程，返还线程资源.*/
            destroy();
            return;
        }
        /*这里注意Keep-alive的周期为free态，bind态，当socket指定时间未绑定/断联后指定时间没有重连，释放socket线程*/
        while (this.state != SocketState.DESTROY){
            //parse Command.
            MDCommand mdCommand = null;
            try {
                try {
                    //协议响应.
                    CommandResponse<JSON> res = null;
                    try {
                        String commandLine = null;
                        while ($STRING_EMPTY.equals(commandLine = reader.readLine())){};
                        mdCommand = CommandDriverManager.parseMDCommand(commandLine.substring(1));
                    }catch (ProtocolUnSupportException e){
                        /*协议解析错误，Command不支持*/
                        res = CommandResponse.error(BizCode.UN_SUPPORT.code,e.message);
                        throw new ProcessorBrokeException(res);
                    }

                    //protocol headers.
                    Map<String,String> headers = new HashMap<>(1<<2);

                    //read protocol headers
                    String line = null;
                    int dataLen = 0;
                    while ((line = reader.readLine()) != null){
                        if(!line.equals($STRING_EMPTY)){
                            String[] split = line.split($COLON);
                            String HEADER = split[0].substring(1);
                            headers.put(HEADER,split[1].trim());
                            if(HEADER.equals(Const.LENGTH)){
                                dataLen = Integer.valueOf(split[1].trim());
                            }
                        }else {
                            break;
                        }
                    }

                    //construct JSON data.
                    Charset charset = Charset.forName(headers.get(Const.CharSet));
                    String data = null;
                    if(dataLen > 0){
                        //Exist Length Clearly, then full data buffer, and parse request. read full data buffer.
                        char[] content = new char[dataLen];
                        int read = reader.read(content);
                        data = new String(content);
                    }else {
                        //request data Length is zero.
                        if(!headers.get(Const.LENGTH).equals(Const._0)){
                            /*协议解析错误，数据传输长度有误 写入客户端*/
                            res = new CommandResponse(BizCode.PROTOCOL_PARSE_ERROR.code,
                                    "Protocol Header: Length error.");
                            throw new ProcessorBrokeException(res);
                        }
                    }

                    //parse data.
                    JSON json = null;
                    try {
                        json = ProtocolUtil.parseRequestData(headers,data);
                    }catch (ProtocolException e){
                        /*协议解析错误, 将异常信息 写入客户端*/
                        res = CommandResponse.error(BizCode.PROTOCOL_PARSE_ERROR.code,e.message);
                        throw new ProcessorBrokeException(res);
                    }

                    //选择CommandDriver实例.
                    MDCommandDriver commandDriver = null;
                    try {
                        commandDriver = CommandDriverManager.chooseMDDriver(mdCommand);
                    } catch (ProtocolUnSupportException e) {
                        /*协议解析错误，Command不支持*/
                        res = CommandResponse.error(BizCode.UN_SUPPORT.code,e.message);
                        throw new ProcessorBrokeException(res);
                    }

                    //执行Command, 获取响应，写回客户端.
                    try {
                        res = commandDriver.chooseAndDeal(mdCommand,this,headers,json);
                    }catch (CommandExecException e){
                        /*执行Command出现异常，写入客户端.*/
                        res = CommandResponse.error(BizCode.ERROR.code,e.message);
                        throw new ProcessorBrokeException(res);
                    }catch (ProtocolUnSupportException e){
                        /*协议不支持，写入客户端.*/
                        res = CommandResponse.error(BizCode.UN_SUPPORT.code,e.message);
                        throw new ProcessorBrokeException(res);
                    }catch (PredicateException e){
                        /*通常为协议Command参数断言失败的运行时异常，抛出protocol异常，写入客户端即可*/
                        res = CommandResponse.error(BizCode.COMMAND_PREDICATE_ERROR.code,e.message);
                        throw new ProcessorBrokeException(res);
                    }

                    //将相应构造CarFunProtocol协议，写回客户端.
                    CarFunProtocol protocol = null;
                    try {
                        protocol = Cast.cast($protocolBuilder.invoke(mdCommand,res.getCode()),CarFunProtocol.class);
                    }catch (Throwable e){
                        /*反射异常，抛服务异常，这里只需要在保证编译阶段无异常基本就不会出现(除了内存堆栈的异常等.)*/
                        throw new ProcessorBrokeException(res);
                    }
                    protocol.charSet(charset);
                    if(res.getData() != null){
                        protocol.data(JSON.toJSONString(res.getData()).getBytes(charset));
                        if(res.getData() instanceof List){
                            protocol.isJSONAry(true);
                        }
                    }
                    StringBuffer responseProtocol = protocol.build();

                    //将响应协议写回客户端.
                    writeToClient(responseProtocol.toString().getBytes(charset));
                }catch (ProcessorBrokeException processorBroke){
                    /*Occur processor error, try to construct response protocol and write to client. */
                    writeToClient(processorBroke.getProtocol());
                }
            }catch (IOException | NullPointerException e){
                /*SocketProxy :: IOException | NullPointerException
                socket client had broken when realLine(),
                server destroy socket and release thread resources.*/
                destroy();
                break;
            }catch (ClientConnectException e){
                /*Client disConnect by command, clear register infos,
                change state to FREE, waiting reConnect.
                客户端连接异常，这里MDCommand只操作由客户端发送来的数据，
                换句话说客户端及时发送数据后立马关闭连接服务端也是未知的，
                不会在MDCommandDriver执行MDCommand的时候进行客户端的write操作，
                除非客户端主动发起CLIENT_CONNECT_END命令，请求服务端断联外
                基本不会发生ClientConnectException.*/
                destroy();
                break;
            }catch (Throwable e){
                /*Server error, developer need to check if anywhere
                throw RuntimeException and has none dealing!!! */
                e.printStackTrace();
                LogUtil.error("SocketProxy :: Throwable :: Developer check if anywhere" +
                        "throw RuntimeException and has none dealing!!!");
            }
        }
    }



    /**
     * Socket异常，
     *  1.立即清理该Socket，
     *  2.从游离态中清理该Socket，
     *  3.清理channel中的注册信息.
     */
    public void destroy(){
        if(this.state == SocketState.DESTROY){
            return;
        }
        //取消自身在channel中的注册.
        clearChannel();
        //从游离态中移除Socket，如果存在
        MindManger.freeSocket.remove(this.identity);
        //状态销毁, 线程资源释放.
        this.state = SocketState.DESTROY;
        //关闭数据流
        try {
            if(reader != null) {
                reader.close();
                reader = null;
            };
            if(outStream != null){
                outStream.close();
                outStream = null;
            }
            if(socket != null){
                socket.close();
                socket = null;
            }
        }catch (IOException e){
            e.printStackTrace();
            /*Resources close occur error will be ignore.*/
        }
    }

    /**
     * 断连，可能因为网络原因，也可能因为客户端发起退出组队.
     * 1.socket置为游离态
     * 2.清理channel中的注册信息
     */
    public void unBind(){
        switch (this.state){
            case FREE :
                break;
            case BIND :
                changeToFree();
                break;
            case DESTROY :
                throw new RuntimeException("Socket has been destroyed.");
            default:
                throw new RuntimeException("State Unsupported.");
        }
    }


    /**
     * 清理channel中客户端的注册信息.
     */
    public void clearChannel(){
        if(this.channelId != null){
            Optional<Channel> channel = Optional.ofNullable(MindManger.channels.get(this.channelId));
            if(channel.isPresent()){
                channel.get().clear(this);
            }
        }
        this.channelId = null;
        this.channel = null;
        this.registerInfo = null;
    }


    /**
     * change socket to free.
     */
    public void changeToFree(){
        clearChannel();
        this.state = SocketState.FREE;
        this.beFreeZone = LocalDateTime.now();
        //游离态的SocketProxy被MindManager管理.
        MindManger.freeSocket.put(identity,this);
    }


    /**
     * SocketProxy bind channel.
     * @param channel
     * @return
     */
    public boolean bindChannel(@NotNull Channel channel,@NotNull RInfo registerInfo){
        this.channel = channel;
        this.channelId = channel.getChannelId();
        this.registerInfo = registerInfo;
        this.state = SocketState.BIND;
        //从MindManager管理的FreeSocket中移出该socket，当前socket处于BIND态
        MindManger.freeSocket.remove(identity);
        this.beFreeZone = null;
        return true;
    }

    /**
     * write to client.
     * @param content
     * @throws IOException
     */
    public void writeToClient(byte[] content) throws IOException{
        if(outStream != null && socket != null){
            IOException IOE = null;
            synchronized (this){
                try {
                    if(outStream != null){
                        outStream.write(content);
                        outStream.flush();
                    }
                }catch (IOException e){
                    IOE = e;
                    /* write occur IOException,
                    client has disconnected,
                    destroy this client and release resources. */
                    destroy();
                }
            }
            if(IOE != null){
                throw IOE;
            }
        }
    }

    @Override
    public String toString() {
        return "SocketProxy{" +
                "socket=" + socket +
                ", state=" + state +
                ", identity=" + identity +
                ", channelId=" + channelId +
                ", beFreeZone=" + beFreeZone +
                '}';
    }

    public static Charset getUtf8() {
        return UTF_8;
    }

    public Socket getSocket() {
        return socket;
    }

    public void setSocket(Socket socket) {
        this.socket = socket;
    }

    public Class<? extends RInfo> getRegInfoClass() {
        return regInfoClass;
    }

    public void setRegInfoClass(Class<? extends RInfo> regInfoClass) {
        this.regInfoClass = regInfoClass;
    }

    public SocketState getState() {
        return state;
    }

    public void setState(SocketState state) {
        this.state = state;
    }

    public Long getIdentity() {
        return identity;
    }

    public Long getChannelId() {
        return channelId;
    }

    public void setChannelId(Long channelId) {
        this.channelId = channelId;
    }

    public Channel getChannel() {
        return channel;
    }

    public void setChannel(Channel channel) {
        this.channel = channel;
    }

    public RInfo getRegisterInfo() {
        return registerInfo;
    }

    public void setRegisterInfo(RInfo registerInfo) {
        this.registerInfo = registerInfo;
    }

    public LocalDateTime getBeFreeZone() {
        return beFreeZone;
    }

    public void setBeFreeZone(LocalDateTime beFreeZone) {
        this.beFreeZone = beFreeZone;
    }

    public static MethodHandle get$protocolBuilder() {
        return $protocolBuilder;
    }

    public BufferedReader getReader() {
        return reader;
    }

    public void setReader(BufferedReader reader) {
        this.reader = reader;
    }

    public OutputStream getOutStream() {
        return outStream;
    }

    public void setOutStream(OutputStream outStream) {
        this.outStream = outStream;
    }
}
